package com.huawang.mdesign.model.service.util.mindmap;

import com.huawang.mdesign.model.service.constant.mindmap.MindMapEnum;
import com.huawang.mdesign.model.service.util.mindmap.xmind.Item;
import com.huawang.mdesign.model.service.util.mindmap.xmind.ItemAttached;
import com.huawang.mdesign.model.service.util.mindmap.xmind.Root;
import com.huawang.mdesign.model.service.util.mindmap.xmind.RootTopic;
import lombok.SneakyThrows;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.*;

/**
 * FreeMind解析类
 *
 * @author admin
 */
@Component
public class FreeMindUtil implements MindMap {

    public static final String NODE = "node";

    public static final String ID = "ID";

    public static final String TEXT = "TEXT";

    public static final String FILE_SYSTEM_PREFIX = "file:";

    @Override
    public boolean support(@NotNull String sourceFile) {
        return StringUtils.isNotBlank(sourceFile) && sourceFile.endsWith(MindMapEnum.FREE_MIND.getSuffix());
    }

    @Override
    public @NotNull List<Root> read(@NotNull String sourceFile) {
        return parse(sourceFile);
    }

    /**
     * 解析FreeMind源文件
     *
     * @param sourceFile 源文件路径
     * @return 树形结构
     */
    @SneakyThrows
    @NotNull
    public static List<Root> parse(@Nullable String sourceFile) {
        if (StringUtils.isBlank(sourceFile) || !sourceFile.endsWith(MindMapEnum.FREE_MIND.getSuffix())) {
            return Collections.emptyList();
        }

        NodeList nodes = parseXml(sourceFile).getElementsByTagName(NODE);

        Item root = new Item();
        getChildren(nodes, root, new HashSet<>());
        return root2List(root);
    }

    /**
     * 解析xml格式的文档
     *
     * @param path 文档路径
     * @return 文档对象
     */
    @SneakyThrows
    @NotNull
    public static Document parseXml(@Nullable String path) {
        DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
        df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
        DocumentBuilder builder = df.newDocumentBuilder();
        return builder.parse(getSystemPath(path));
    }

    /**
     * 获取文件系统路径
     *
     * @param sourceFile 源文件路径
     * @return 文件系统路径
     */
    @Nullable
    public static String getSystemPath(@Nullable String sourceFile) {
        return StringUtils.isNotBlank(sourceFile) && !sourceFile.startsWith(FILE_SYSTEM_PREFIX) ?
                FILE_SYSTEM_PREFIX + "/" + sourceFile : sourceFile;
    }

    /**
     * 转换根节点
     *
     * @param item 根节点
     * @return 转换结果
     */
    @NotNull
    public static List<Root> root2List(@Nullable Item item) {
        return Optional.ofNullable(item)
                .map(Item::getChildren)
                .map(ItemAttached::getAttached)
                .filter(CollectionUtils::isNotEmpty)
                .map(e -> e.get(0))
                .map(e -> {
                    RootTopic rootTopic = new RootTopic();
                    rootTopic.setId(e.getId());
                    rootTopic.setTitle(e.getTitle());
                    rootTopic.setChildren(e.getChildren());
                    return rootTopic;
                })
                .map(e -> {
                    List<Root> res = new ArrayList<>();
                    Root root = new Root();
                    res.add(root);
                    root.setRootTopic(e);
                    return res;
                }).orElse(Collections.emptyList());
    }

    /**
     * 获取子节点
     *
     * @param nodes  子节点
     * @param parent 父节点
     * @param ids    所有节点集合
     */
    private static void getChildren(@NotNull NodeList nodes, @Nullable Item parent, @NotNull Set<String> ids) {
        List<Item> children = new ArrayList<>();
        for (int i = 0, len = nodes.getLength(); i < len; ++i) {
            Node item = nodes.item(i);
            if (item.getNodeType() == Node.ELEMENT_NODE) {
                Element node = (Element) item;
                String id = node.getAttribute(ID);
                if (ids.contains(id)) {
                    continue;
                }

                ids.add(id);
                Item e = new Item();
                e.setId(id);
                e.setTitle(node.getAttribute(TEXT));
                children.add(e);
                getChildren(item.getChildNodes(), e, ids);
            }
        }

        setChildren(children, parent);
    }

    /**
     * 设置子节点
     *
     * @param children 子节点
     * @param parent   父节点
     */
    public static void setChildren(@Nullable List<Item> children, @Nullable Item parent) {
        Optional.ofNullable(children)
                .filter(CollectionUtils::isNotEmpty)
                .ifPresent(e -> {
                    ItemAttached attached = new ItemAttached();
                    attached.setAttached(e);
                    Optional.ofNullable(parent).ifPresent(p -> p.setChildren(attached));
                });
    }
}
